package de.umr.fb12.vs.os;

import static de.umr.fb12.vs.os.OpCodes.ADD;
import static de.umr.fb12.vs.os.OpCodes.AND;
import static de.umr.fb12.vs.os.OpCodes.ARG0;
import static de.umr.fb12.vs.os.OpCodes.ARG1;
import static de.umr.fb12.vs.os.OpCodes.ARG2;
import static de.umr.fb12.vs.os.OpCodes.ARGT_MEM;
import static de.umr.fb12.vs.os.OpCodes.ARGT_REG;
import static de.umr.fb12.vs.os.OpCodes.ARGT_VAL;
import static de.umr.fb12.vs.os.OpCodes.DIV;
import static de.umr.fb12.vs.os.OpCodes.HLT;
import static de.umr.fb12.vs.os.OpCodes.INT;
import static de.umr.fb12.vs.os.OpCodes.JEQ;
import static de.umr.fb12.vs.os.OpCodes.JGE;
import static de.umr.fb12.vs.os.OpCodes.JGT;
import static de.umr.fb12.vs.os.OpCodes.JLE;
import static de.umr.fb12.vs.os.OpCodes.JLT;
import static de.umr.fb12.vs.os.OpCodes.JMP;
import static de.umr.fb12.vs.os.OpCodes.JNE;
import static de.umr.fb12.vs.os.OpCodes.MOD;
import static de.umr.fb12.vs.os.OpCodes.MOV;
import static de.umr.fb12.vs.os.OpCodes.MUL;
import static de.umr.fb12.vs.os.OpCodes.NOT;
import static de.umr.fb12.vs.os.OpCodes.OR;
import static de.umr.fb12.vs.os.OpCodes.SHL;
import static de.umr.fb12.vs.os.OpCodes.SHR;
import static de.umr.fb12.vs.os.OpCodes.SOP;
import static de.umr.fb12.vs.os.OpCodes.SUB;
import static de.umr.fb12.vs.os.OpCodes.XOR;

import java.util.Random;

/**
 * 
 * CPU is the wrapper class for the execution unit: Core
 * 
 */
class CPU {
	Core core;
	Boolean irq_pending = false;
	static final int REG_COUNT = 16;
	static final int MEM_SIZE = 4096;
	volatile Task current_task;
	IRQController irq_ctrl;
	int cpuid;
	private static int CPUIDS = 0;
	volatile long user, system = 0;
	// irq handler table
	IRQHandler[] handler = new IRQHandler[150];

	/**
	 * 
	 * Core is a thread implementation which executes the programs of a task and
	 * jumps into the scheduler
	 * 
	 */

	class Core extends Thread {
		int oip; // old instruction pointer
		int op; // opcode
		int argp; // argument pointer

		@Override
		public void run() {
			main_loop: for (;;) {
				// if a irq is pending: Handle it!
				if (irq_pending) {
					long start = System.nanoTime();
					// handle the irq
					CPU.this.serve_irq();
					irq_pending = false;
					// signal that the irq was handled
					irq_ctrl.active_irq.release();
					// update the time the cpu spent in "sys-mode"
					system += System.nanoTime() - start;
				}
				// if we have a task ...
				if (current_task != null) {
					try {
						// ... execute it
						for (;;)
							step();
					} catch (PendingIRQException e) {
						// catch the PendingIRQ and jump to irq handling
						continue main_loop;
					}
				}
			}
		}

		/**
		 * Step executes the next operation in the current task
		 */
		private void step() {
			// if there is a pending irq raise a exception
			if (irq_pending)
				throw new PendingIRQException();
			if (current_task.state != Task.STATES.RUNNING) {
				System.err.printf("Process # %d is running but %s%n",
						current_task.pid, current_task.state);
			}
			oip = current_task.ip;
			op = current_task.mem[current_task.ip++];
			argp = current_task.ip;
			current_task.ip += op >>> 30;

			try {
				switch (op & 0x00FFFFFF) {
				case HLT:
					throw new RuntimeException("HLT not supported");
				case INT:
					if (getV(ARG0) == 120)
						entry();
					else
						throw new UnsupportedOperationException("Invalid INT!");
					break;
				case SOP:
					switch (getV(ARG0)) {
					case 1: // random
						setReg(8, new Random().nextInt(getReg(1)));
						break;
					case 2:
						synchronized (current_task) {
							try {
								current_task.wait();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						break;
					case 3: // some work
						setReg(8, new Random().nextInt(getReg(1)));
						break;
					default:
						throw new UnsupportedOperationException("Invalid SOP!");
					}
					break;
				case JMP:
					// use getV here, because the address itself is
					// required,
					// not the value stored at the address specified by the
					// argument
					current_task.ip = getV(ARG0);
					break;

				case JLT:
					if (getRM(ARG0) < getVRM(ARG1))
						// use getV here, because the address itself is
						// required,
						// not the value stored at the address specified by
						// the argument
						current_task.ip = getV(ARG2);
					break;

				case JLE:
					if (getRM(ARG0) <= getVRM(ARG1))
						// use getV here, because the address itself is
						// required,
						// not the value stored at the address specified by
						// the argument
						current_task.ip = getV(ARG2);
					break;
				case JEQ:
					if (getRM(ARG0) == getVRM(ARG1))
						// use getV here, because the address itself is
						// required,
						// not the value stored at the address specified by
						// the argument
						current_task.ip = getV(ARG2);
					break;
				case JNE:
					if (getRM(ARG0) != getVRM(ARG1))
						// use getV here, because the address itself is
						// required,
						// not the value stored at the address specified by
						// the argument
						current_task.ip = getV(ARG2);
					break;
				case JGT:
					if (getRM(ARG0) > getVRM(ARG1))
						// use getV here, because the address itself is
						// required,
						// not the value stored at the address specified by
						// the argument
						current_task.ip = getV(ARG2);
					break;
				case JGE:
					if (getRM(ARG0) >= getVRM(ARG1))
						// use getV here, because the address itself is
						// required,
						// not the value stored at the address specified by
						// the argument
						current_task.ip = getV(ARG2);
					break;
				case MOV:
					setRM(ARG0, getVRM(ARG1));
					break;
				case ADD:
					setRM(ARG0, getRM(ARG0) + getVRM(ARG1));
					break;

				case SUB:
					setRM(ARG0, getRM(ARG0) - getVRM(ARG1));
					break;

				case MUL:
					setRM(ARG0, getRM(ARG0) * getVRM(ARG1));
					break;

				case DIV:
					setRM(ARG0, getRM(ARG0) / getVRM(ARG1));
					break;

				case MOD:
					setRM(ARG0, getRM(ARG0) % getVRM(ARG1));
					break;

				case NOT:
					setRM(ARG0, ~getRM(ARG0));
					break;

				case AND:
					setRM(ARG0, getRM(ARG0) & getVRM(ARG1));
					break;

				case OR:
					setRM(ARG0, getRM(ARG0) | getVRM(ARG1));
					break;

				case XOR:
					setRM(ARG0, getRM(ARG0) ^ getVRM(ARG1));
					break;

				case SHR:
					setRM(ARG0, getRM(ARG0) >>> getVRM(ARG1));
					break;

				case SHL:
					setRM(ARG0, getRM(ARG0) << getVRM(ARG1));
					break;
				}
			} catch (PendingIRQException e) {
				current_task.ip = oip;
				throw e;
			}

		}

		private void entry() {
			switch (getReg(0)) {
			case Syscall.SC_YIELD:
				Syscall.do_yield(current_task);
				break;
			case Syscall.SC_IOREQ:
				Syscall.do_IO_req(current_task, getReg(1));
				break;
			case Syscall.SC_DUMMY:
				Syscall.do_dummy(current_task);
				break;
			case Syscall.SC_EXIT:
				Syscall.do_exit(current_task);
				break;
			case Syscall.SC_EXECVE:
				Syscall.do_execve(current_task, getReg(1));
				break;
			default:
				throw new RuntimeException("Invalid Syscall");
			}
		}

		/**
		 * parse the argument type of arg
		 * 
		 * @param arg
		 * @return
		 */
		private int argType(int arg) {
			assert arg >= 0 && arg <= 2;
			int argType = (op >>> (28 - (arg * 2))) & 3;
			if (argType < ARGT_VAL || argType > ARGT_MEM)
				throw new IllegalArgumentTypeException(oip, arg);
			return argType;
		}

		/**
		 * read the argument arg from memory (as value)
		 * 
		 * @param arg
		 */
		private int getV(int arg) {
			assert arg >= 0 && arg <= 2;
			return current_task.mem[argp + arg];
		}

		private int getVRM(int argNumber) {
			int source = getV(argNumber);
			switch (argType(argNumber)) {
			case ARGT_VAL:
				return source;
			case ARGT_REG:
				if (source < 0 || source >= REG_COUNT)
					throw new IllegalRegisterNumberException(oip, argNumber,
							source);
				return current_task.reg[source];
			case ARGT_MEM:
				if (source < 0 || source >= MEM_SIZE)
					throw new IllegalMemoryAddressException(oip, argNumber,
							source);
				return current_task.mem[source];
			default:
				throw new Error("Should never be reached");
			}
		}

		private int getRM(int argNumber) {
			int source = getV(argNumber);
			switch (argType(argNumber)) {
			case ARGT_VAL:
				throw new IllegalArgumentTypeException(oip, argNumber);
			case ARGT_REG:
				if (source < 0 || source >= REG_COUNT)
					throw new IllegalRegisterNumberException(oip, argNumber,
							source);
				return current_task.reg[source];
			case ARGT_MEM:
				if (source < 0 || source >= MEM_SIZE)
					throw new IllegalMemoryAddressException(oip, argNumber,
							source);
				return current_task.mem[source];
			default:
				throw new Error("Should never be reached");
			}
		}

		private void setRM(int argNumber, int newVal) {
			int target = getV(argNumber);
			switch (argType(argNumber)) {
			case ARGT_VAL:
				throw new IllegalAssignmentException(oip, argNumber);
			case ARGT_REG:
				if (target < 0 || target >= REG_COUNT)
					throw new IllegalRegisterNumberException(oip, argNumber,
							target);
				current_task.reg[target] = newVal;
				break;
			case ARGT_MEM:
				if (target < 0 || target >= MEM_SIZE)
					throw new IllegalMemoryAddressException(oip, argNumber,
							target);
				current_task.mem[target] = newVal;
				break;
			default:
				throw new Error("Should never be reached");
			}
		}

		int getReg(int number) {
			if (number < 0 || number >= REG_COUNT)
				throw new IllegalRegisterNumberException(-1, -1, -1);
			return current_task.reg[number];
		}

		void setReg(int number, int value) {
			if (number < 0 || number >= REG_COUNT)
				throw new IllegalRegisterNumberException(-1, -1, -1);
			current_task.reg[number] = value;
		}

		int getMem(int address) {
			if (address < 0 || address >= MEM_SIZE)
				throw new IllegalMemoryAddressException(-1, -1, -1);
			return current_task.mem[address];
		}

		synchronized void raiselocalIRQ() {
			irq_pending = true;
			// wake up mainly idle_task
			synchronized (current_task) {
				current_task.notify();
			}
		}

	}

	CPU() {
		cpuid = CPUIDS++;
		irq_ctrl = new IRQController(this);
		core = new Core();
		core.start();
	}

	void assign(Task task) {
		if (current_task != null) {
			synchronized (current_task) {
				current_task.notify();
			}
			if (current_task.state == Task.STATES.RUNNING)
				current_task.state = Task.STATES.RUNNABLE;
			current_task.usr += System.nanoTime() - user - system;
			current_task.sys += system;

		}
		task.state = Task.STATES.RUNNING;
		system = 0;
		user = System.nanoTime();
		current_task = task;

	}

	void raiselocalIRQ() {
		core.raiselocalIRQ();

	}

	void disable_local_IRQs() {
		try {
			irq_ctrl.lock.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	void enable_local_IRQs() {
		irq_ctrl.lock.release();
	}

	void register_irq_handler(int irq, IRQHandler handler) {
		this.handler[irq] = handler;
	}

	void serve_irq() {
		handler[irq_ctrl.IRQ_type].handle_IRQ(irq_ctrl.IRQ_type, irq_ctrl.dev);
	}

	@SuppressWarnings("serial")
	private class IllegalArgumentTypeException extends RuntimeException {
		IllegalArgumentTypeException(int ip, int argNumber) {
			super(String.format(
					"argument %d of opcode at %d has an illegal argument type",
					argNumber, ip));
		}
	}

	@SuppressWarnings("serial")
	private class IllegalAssignmentException extends RuntimeException {
		IllegalAssignmentException(int ip, int argNumber) {
			super(String.format(
					"argument %d of opcode at %d is not assignable", argNumber,
					ip));
		}
	}

	@SuppressWarnings("serial")
	private class IllegalMemoryAddressException extends RuntimeException {
		IllegalMemoryAddressException(int ip, int argNumber, int address) {
			super(String.format(
					"argument %d of opcode at %d points to invalid address %d",
					argNumber, ip, address));
		}
	}

	@SuppressWarnings("serial")
	private class IllegalRegisterNumberException extends RuntimeException {
		IllegalRegisterNumberException(int ip, int argNumber, int register) {
			super(
					String.format(
							"argument %d of opcode at %d points to invalid register %d",
							argNumber, ip, register));
		}
	}

	@SuppressWarnings("serial")
	static class PendingIRQException extends RuntimeException {
	}

	void wakeUpIDLE() {
		synchronized (current_task) {
			current_task.notify();
		}
	}
}
